﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace JumpNFight.Simulation
{
    /// <summary>
    /// Defines a moving game entity
    /// </summary>
    class Moveable
    {
        BoundingBox m_bbox = new BoundingBox();
       
        public int OwnerID
        {
            get;
            private set;
        }

        /// <summary>
        /// World position (in pixel units)
        /// </summary>
        public Vector2 Position;

        /// <summary>
        /// Gets X coordinates
        /// </summary>
        public float X
        {
            get
            {
                return Position.X;
            }
        }

        /// <summary>
        /// Gets Y coordinates
        /// </summary>
        public float Y
        {
            get
            {
                return Position.Y;
            }
        }

        /// <summary>
        /// Entity velocity
        /// </summary>
        public Vector2 Velocity;

        /// <summary>
        /// Gets velocity X value
        /// </summary>
        public float VX
        {
            get
            {
                return Velocity.X;
            }
        }

        /// <summary>
        /// Gets velocity Y value
        /// </summary>
        public float VY
        {
            get
            {
                return Velocity.Y;
            }
        }

        /// <summary>
        /// Wall drag 
        /// </summary>
        public float Drag = 0;

        /// <summary>
        /// Ground drag
        /// </summary>
        public float GroundDrag = 5f;

        /// <summary>
        /// Mass 
        /// </summary>
        public float Mass = 1.0f;

        /// <summary>
        /// Indicates whether the entity is touching ground or not
        /// </summary>
        public bool TouchingGround = false;

        /// <summary>
        /// Max velocity
        /// </summary>
        public float MaxSpeed = 150;

        /// <summary>
        /// Tile X coordinate
        /// </summary>
        public int TileX = 0;
        /// <summary>
        /// Tile Y coordinate
        /// </summary>
        public int TileY = 0;

        /// <summary>
        /// Entity width (used for world collision)
        /// </summary>
        /// <remarks>
        /// In pixel units.
        /// </remarks>
        public int Width = 32;

        /// <summary>
        /// Entity height (used for world collision)
        /// </summary>
        /// <remarks>
        /// In pixel units
        /// </remarks>
        public int Height = 32;

        public Moveable(int ownerID)
        {
            this.OwnerID = ownerID;
        }

        public virtual void Move(Vector2 direction)
        {
            Velocity.X += MaxSpeed / 4 * direction.X;
            if (Velocity.X > MaxSpeed)
                Velocity.X = MaxSpeed;
            if (Velocity.X < -MaxSpeed)
                Velocity.X = -MaxSpeed;
        }

        public void Push(float force)
        {
            Velocity.X += force;
            if (Velocity.X > MaxSpeed)
                Velocity.X = MaxSpeed;
            if (Velocity.X < -MaxSpeed)
                Velocity.X = -MaxSpeed;
        }

        public void Push(Vector2 force)
        {
            Velocity.X = MathHelper.Clamp(Velocity.X + force.X, -MaxSpeed, MaxSpeed);
            Velocity.Y = MathHelper.Clamp(Velocity.Y + force.Y, -MaxSpeed, MaxSpeed);
        }

        /// <summary>
        /// Update velocity
        /// </summary>
        /// <param name="dt">Delta time</param>
        /// <remarks>
        /// To be called by actor owner (e.g. world) before actual gets adjusted
        /// </remarks>
        public virtual void UpdateVelocity(float dt)
        {
            // update velocity   
            if (TouchingGround)
            {
                // drag
                Velocity.X += -dt * GroundDrag * Velocity.X;
            }
            else
            {
                // gravity
                Velocity.X += dt * Mass * World.Gravity.X;
                Velocity.Y += dt * Mass * World.Gravity.Y;
            }

            // use drag from previous update pass
            Velocity.Y += dt * Drag * Velocity.Y;
        }

        public void Teleport(Vector2 position)
        {
            // now update position
            Position = position;

            // update tile position
            TileX = (int)Math.Floor(Position.X / World.UnitToPixel);
            TileY = (int)Math.Floor(Position.Y / World.UnitToPixel);

            // update bounding box
            m_bbox.Min = new Vector3(Position.X - Width / 2, Position.Y - Height / 2, 0);
            m_bbox.Max = new Vector3(Position.X + Width / 2, Position.Y + Height / 2, 0);
        }

        /// <summary>
        /// Update position
        /// </summary>
        /// <param name="dt">Delta time</param>
        public virtual void UpdatePosition(float dt)
        {
            // now update position
            Position.X += dt * Velocity.X;
            Position.Y += dt * Velocity.Y;

            // update tile position
            TileX = (int)Math.Floor(Position.X / World.UnitToPixel);
            TileY = (int)Math.Floor(Position.Y / World.UnitToPixel);

            // update bounding box
            m_bbox.Min = new Vector3(Position.X - Width / 2, Position.Y - Height / 2, 0);
            m_bbox.Max = new Vector3(Position.X + Width / 2, Position.Y + Height / 2, 0);
        }

        public bool Intersects(Ray r, float length)
        {
            Nullable<float> result;
            m_bbox.Intersects(ref r, out result);
            // do we have an intersection within the segment?
            return result.HasValue && result.Value >= 0 && result.Value <= length;
        }

        public bool Intersects(Capsule2d capsule)
        {
            return capsule.Intersects(m_bbox);
        }

        public bool Intersects(BoundingSphere bs)
        {
            return m_bbox.Intersects(bs);
        }

        public bool Intersects(Moveable other)
        {
            return m_bbox.Intersects(other.m_bbox);
        }

        public bool Intersects(BoundingBox otherBBox)
        {
            return m_bbox.Intersects(otherBBox);
        }

        public void Reset()
        {
            TileX = -1;
            TileY = -1;
            Position = Vector2.Zero;
            Velocity = Vector2.Zero;
            Drag = 0;
            TouchingGround = false;
        }

        public virtual void HitWall()
        {
            Velocity.X = 0;
            if (Velocity.Y > 0)
            {
                Drag = -15f;
            }
        }
        public virtual void HitCeiling()
        {
            Velocity.Y = 0;
        }
        public virtual void HitFloor()
        {
            Velocity.Y = 0;
        }
    }
}
